'use strict';

import * as d3 from 'd3';

import { Vector3 } from '@babylonjs/core/Maths/math.vector'
import { Color3, Color4 } from '@babylonjs/core/Maths/math.color'
import { Texture } from '@babylonjs/core/Materials/Textures/texture'
import { Mesh } from '@babylonjs/core/Meshes/mesh'
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder'
import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial'
import { PointerEventTypes } from '@babylonjs/core/Events/pointerEvents'


export default class {
  constructor(scene) {
    this.scene = scene;
    this.checkerBoard = 'iVBORw0KGgoAAAANSUhEUgAAAQEAAAEBCAIAAAD3joeqAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAALnSURBVHhe7dZBEYQwFAVBWBtogAv+IhIEsZd4yGG6L/lPwFRl31jqPM/neeZghd98oUoD1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjTAHUaoE4D1GmAOg1QpwHqNECdBqjb7/ueJyscxzHGmIMV9u/75skK7/te1zUHK/gLUacB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA9RpgDoNUKcB6jRAnQao0wB1GqBOA7Rt2x+drw1hSNi5LQAAAABJRU5ErkJggg==';
    this.xmark = '';
    this.buildObjectMeshes = new Array();
    this.labels = new Array();
    this.labelSVGS = new Array();
    this.baseMaterial;
    this.highlightMaterial;
    this.cancelledMaterial;
    this.cancelledHighlightMaterial;
    this.showCancelObjects = false;
    this.objectCallback;
    this.renderFailedCallback;
    this.labelCallback;
    this.registerClipIgnore;
    this.getMaxHeight;
    this.alphaLevel = 0.5;

    this.observableControls = null;

    this.showLabel = localStorage.getItem('showObjectLabels');
    if (this.showLabel === null) {
      this.showLabel = true;
    } else {
      this.showLabel = JSON.parse(this.showLabel);
    }

    this.rebuildMaterials();
  }

  setBuildMaterial(name, color, alpha) {
    if (!alpha) {
      alpha = this.alphaLevel;
    }

    let material = new StandardMaterial(name, this.scene);
    material.diffuseColor = color;
    material.specularColor = new Color3(0.0, 0.0, 0.0);
    material.alpha = alpha;
    material.needAlphaTesting = () => true;
    material.separateCullingPass = true;
    material.backFaceCulling = true;
    return material;
  }
  rebuildMaterials() {
    this.baseMaterial = this.setBuildMaterial('BuildObjectBaseMaterial', new Color4(0.1, 0.5, 0.1), 0.25);
    this.highlightMaterial = this.setBuildMaterial('BuildObjectHighlightMateria', new Color3(0.8, 0.8, 0.8));
    this.cancelledMaterial = this.setBuildMaterial('BuildObjectHighlightMateria', new Color3(1, 0, 0), 0.4);
    this.cancelledHighlightMaterial = this.setBuildMaterial('BuildObjectHighlightMateria', new Color3(1, 1, 0), 0.6);
    let material = new Texture.CreateFromBase64String(this.xmark, 'checkerboard', this.scene);
    this.cancelledMaterial.diffuseTexture = material;
    this.cancelledHighlightMaterial.diffuseTexture = material;
  }
  loadObjectBoundaries(boundaryObjects) {
    this.rebuildMaterials();
    if (this.buildObjectMeshes.length > 0) {
      for (let i = 0; i < this.buildObjectMeshes.length; i++) {
        this.buildObjectMeshes[i].dispose();
      }
      this.labelSVGS.forEach((label) => window.URL.revokeObjectURL(label));
      this.buildObjectMeshes = new Array();
      this.labels = new Array();
    }

    if (!boundaryObjects) {
      return;
    }

    for (let cancelObjectIdx = 0; cancelObjectIdx < boundaryObjects.length; cancelObjectIdx++) {
      let cancelObject = boundaryObjects[cancelObjectIdx];

      let buildObject = MeshBuilder.CreateTiledBox(
        'OBJECTMESH:' + cancelObject.name,
        {
          pattern: Mesh.CAP_ALL,
          alignVertical: Mesh.TOP,
          alignHorizontal: Mesh.LEFT,
          tileHeight: 4,
          tileWidth: 4,
          width: Math.abs(cancelObject.x[1] - cancelObject.x[0]),
          height: this.getMaxHeight() + 10,
          depth: Math.abs(cancelObject.y[1] - cancelObject.y[0]),
          sideOrientation: Mesh.FRONTSIDE,
        },
        this.scene
      );

      buildObject.position.x = (cancelObject.x[1] + cancelObject.x[0]) / 2;
      buildObject.position.y = this.getMaxHeight() / 2 - 4;
      buildObject.position.z = (cancelObject.y[1] + cancelObject.y[0]) / 2;
      buildObject.alphaIndex = 5000000;
      cancelObject.index = cancelObjectIdx;
      buildObject.metadata = cancelObject;
      buildObject.enablePointerMoveEvents = true;
      buildObject.renderingGroupId = 3;
      this.setObjectTexture(buildObject);
      buildObject.setEnabled(this.showCancelObjects);
      this.registerClipIgnore(buildObject);
      this.buildObjectMeshes.push(buildObject);

      //generate a label

      var textPlane = this.makeTextPlane(cancelObject.name, cancelObject.cancelled ? 'yellow' : 'white', 20);
      textPlane.position = new Vector3(0, this.getMaxHeight() / 2 + 10, 0);
      textPlane.isPickable = false;
      textPlane.metadata = cancelObject;
      textPlane.parent = buildObject;
      textPlane.setEnabled(this.showLabel);
      this.labels.push(textPlane);
    }
  }
  makeTextPlane(text, color, size) {
    /*
    var dynamicTexture = new DynamicTexture('DynamicTexture', { width: text.length * 20, height: 200 }, this.scene, true);
    dynamicTexture.hasAlpha = true;
    dynamicTexture.drawText(text, null, null, 'bold 24px Roboto', color, 'transparent', true);*/
    var svg = d3
      .create('svg')
      .attr('width', 800)
      .attr('height', 200)
      .attr('fill', 'none');

    svg
      .append('text')
      .attr('x', 400)
      .attr('y', 100)
      .attr('font-family', 'Verdana')
      .attr('font-size', '50px')
      .attr('text-anchor', 'middle')
      .attr('alignment-baseline', 'middle')
      .attr('fill', 'black')
      .attr('stroke', color)
      .attr('stroke-width', 2)
      .attr('text-rendering', 'optimizeLegibility')
      .text(text);

    var html = svg
      .attr('title', 'test2')
      .attr('version', 1.1)
      .attr('xmlns', 'http://www.w3.org/2000/svg')
      .node(); //.parentNode.innerHTML;

    var doctype = '<?xml version="1.0" standalone="no"?>' + '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';

    var source = new XMLSerializer().serializeToString(html);
    var blob = new Blob([doctype + source], { type: 'image/svg+xml' });
    var url = window.URL.createObjectURL(blob);
    this.labelSVGS.push(url);

    let plane = MeshBuilder.CreatePlane('TextPlane', { width: size, height: 8 }, this.scene);
    plane.material = new StandardMaterial('TextPlaneMaterial', this.scene);
    plane.material.backFaceCulling = false;
    plane.material.specularColor = new Color3(0, 0, 0);
    plane.material.diffuseTexture = new Texture(url, this.scene); //dynamicTexture;
    plane.material.diffuseTexture.hasAlpha = true;
    plane.billboardMode = 7;
    this.registerClipIgnore(plane);
    return plane;
  }

  buildObservables() {
    if (this.observableControls) {
      return;
    }

    let hitTestTimer  =0;
    let mouseDown  = false;
    let cancelHitTimer = 0;
    
    this.observableControls = this.scene.onPointerObservable.add((pointerInfo) => {
      
      
      let pickInfo = pointerInfo.pickInfo;
      switch (pointerInfo.type) {
        case PointerEventTypes.POINTERDOWN:
          {
            mouseDown = true;
            cancelHitTimer = Date.now();
          }
          break;
        case PointerEventTypes.POINTERUP:
          {
            mouseDown = false;
            if (Date.now() - cancelHitTimer > 200) {
              return;
            }
            this.handleClick(pickInfo);
          }
          break;
        case PointerEventTypes.POINTERMOVE: {
          if(mouseDown || Date.now() - hitTestTimer < 100) return;
          hitTestTimer = Date.now();
          this.handlePointerMove(pickInfo);
        }
      }
    });
  }

  clearObservables() {
    if (this.observableControls) {
      this.scene.onPointerObservable.remove(this.observableControls);
      this.observableControls = null;
    }
  }

  showObjectSelection(visible) {
    this.showCancelObjects = visible;
    this.buildObjectMeshes.forEach((mesh) => mesh.setEnabled(visible));

    if (visible) {
      this.buildObservables();
    } else {
      this.clearObservables();
    }
  }
  setObjectTexture(mesh) {
    if (mesh.metadata.cancelled) {
      mesh.material = this.cancelledMaterial;
      mesh.enableEdgesRendering();
      mesh.edgesWidth = 15.0;
      mesh.edgesColor = new Color4(1, 0, 0, 1);
    } else {
      mesh.material = this.baseMaterial;
      mesh.enableEdgesRendering();
      mesh.edgesWidth = 15.0;
      mesh.edgesColor = new Color4(0, 1, 0, 1);
    }
  }
  handleClick(pickInfo) {
    if (!this.showCancelObjects) return;
    if (pickInfo.hit && pickInfo.pickedMesh && pickInfo.pickedMesh.name.includes('OBJECTMESH') && this.objectCallback) {
      this.objectCallback(pickInfo.pickedMesh.metadata);
    }
  }
  handlePointerMove(pickInfo) {
    if (!this.showCancelObjects) return;
    this.buildObjectMeshes.forEach((mesh) => this.setObjectTexture(mesh));
    if (pickInfo.hit && pickInfo.pickedMesh && pickInfo.pickedMesh.name.includes('OBJECTMESH')) {
      pickInfo.pickedMesh.material = pickInfo.pickedMesh.metadata.cancelled ? this.cancelledHighlightMaterial : this.highlightMaterial;
      if (this.labelCallback) {
        this.labelCallback(pickInfo.pickedMesh.metadata.name);
      }
    } else {
      if (this.labelCallback) {
        this.labelCallback('');
      }
    }
  }
  showLabels(visible) {
    localStorage.setItem('showObjectLabels', visible);
    this.showLabel = visible;
    this.labels.forEach((label) => label.setEnabled(visible));
  }
}
